package awdc;

//At this moment (30.03.2010) we can share only one directory.
//We'll use this class both for our share and filelists of another people

import java.io.*; 
import java.security.*; 
import jonelo.jacksum.*; 
import jonelo.jacksum.algorithm.*; 
import javax.xml.parsers.*;

//import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.*;
import javax.xml.transform.*;
import javax.xml.transform.dom.*;
import javax.xml.transform.stream.*;


import java.util.regex.*;

//4 bzipping
import org.apache.tools.bzip2.*;



public class Shara {
	
	//TODO убрать отсюда дефолтные определения
	private String SharedDirectoryName = "/home/alexwinner/workspace/awdc/share";
	private String FileListName = "/home/alexwinner/workspace/awdc/filelist";
	private String BzippedFileListName = "/home/alexwinner/workspace/awdc/filelist.bz2";
	private boolean thereIsSharedDir = false;
	private Document FileList;
	private File RealSharedDirectory;
	private File RealFileList;

	//Constructor. It creates our own share
	Shara(String SharedDirectoryName,String FileListName){
		Logger L = new Logger();
		//setting value of shareddirectory name and checking this dir
		this.SharedDirectoryName = SharedDirectoryName;
		RealSharedDirectory = new File(SharedDirectoryName);
		if (RealSharedDirectory.exists() && RealSharedDirectory.isDirectory()){
			this.FileListName = FileListName;
			RealFileList = new File(FileListName);
			if (!(RealFileList.isDirectory()) && (RealFileList.canWrite() || !(RealFileList.exists()))){
				/////Случай, когда все ок. Надо делать шару и прочее.
				
			} else 
				System.out.println ("Some troubles with filelist\nisDirectory - " + RealFileList.isDirectory()+"; can write - " + RealFileList.canWrite());

		} else
			//System.out.println("There is no share!!!!");
			L.makeLog("There is now share!");
		Document doc = this.getNewFileListDocument();
		System.out.println(doc.getFirstChild().getNodeName());
		Element RootSharedDirectory = this.getFileElement(this.SharedDirectoryName,doc);
		doc.getFirstChild().appendChild(RootSharedDirectory);
		System.out.println(doc.getFirstChild().getFirstChild().getNodeName());
		System.out.println(doc.getFirstChild().getFirstChild().getNodeName());
		saveXMLFile(doc, FileListName,"utf-8");
		xmlToBzip(FileListName,BzippedFileListName);

		//Initialization
		this.FileList = doc;
		showFileList();
	}

	//Little Constructor
	Shara(){
	}	

	//Constructor. It creates XML in memory from bzipped files
	Shara(String BzippedFileListName) throws Exception {
		this.BzippedFileListName = BzippedFileListName;
		File BzippedFile = new File(BzippedFileListName);
		//Some checks if we can read this file and it is a file
		if (BzippedFile.isFile() && BzippedFile.canRead() && BzippedFile.exists()){
			FileInputStream fis = null;
			FileOutputStream fos = null;
			CBZip2InputStream cbzipis = null;
			byte [] Data = null;
			int count = 0;
			boolean append = false;
			try {
				fis = new FileInputStream(BzippedFileListName);
				//we need to read 2 bytes (BZ)
				Data = new byte[2];
				fis.read(Data);
				//and now we create dom document just from unbzipped stream
				cbzipis = new CBZip2InputStream(fis);
				this.FileList = getDomDocument(cbzipis);
				showFileList();
				//Element el = FileList.getDocumentElement();

				//System.out.println(el.getChildNodes().item(2).getNodeName());
				
			} catch (Exception e){
				new Logger().makeLog("Shara.Shara(String BzippedFileListName): Can't parse received document");
				throw e;
			}
			
		} else
			throw new Exception("Shara.Shara(String BzippedFileListName):Some troubles with bzipped file list: " + BzippedFileListName);//TODO we need to create own exception class, i think
	}		

	//This method is just for testing, it shows all tags from this.FileList by order
	void showFileList(){
		Element el = FileList.getDocumentElement();
		showElement(el);
	}

	//this method recursively shows node names from main filelist element
	void showElement(Node nd){
		String nn = nd.getNodeName();
		if (!nn.equals("#text"))
			System.out.println(nd.getNodeName());
		if (nd.hasAttributes()){
			NamedNodeMap nnm= nd.getAttributes();
			Node AttrName = nnm.getNamedItem("Name");
			if (AttrName != null){
				System.out.println(AttrName.getNodeValue());
			}
		}
		NodeList nl = nd.getChildNodes();
		for (int i=0;i < nl.getLength();i++)
			showElement(nl.item(i));
	}
	//This method creates filelist
	void createNewFileList(){
		if (thereIsSharedDir){
			//At first, we need to check if there is such directory


		}
	}

	//This method sets shared directory value
	void setSharedDirectory(String SharedDirectoryName){
		this.SharedDirectoryName = SharedDirectoryName;
		this.thereIsSharedDir = true;
	}



	//This method is only for test shara
	public static void main(String[] args){
		Shara share = new Shara ("/home/alexwinner/workspace/awdc/share/","/home/alexwinner/workspace/awdc/filelist");
		try {
			Shara share2 = new Shara ("/home/alexwinner/workspace/awdc/filelist.bz2");
			SearchParams SP = null;
			share2.doSearch(SP);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	//This method will be used in constructor for downloaded filelists
	Document getDomDocument(InputStream is) throws Exception{
		DocumentBuilderFactory Fact = DocumentBuilderFactory.newInstance();
		DocumentBuilder Bd = null;
		Document doc = null;
			Bd = Fact.newDocumentBuilder();
			doc = Bd.parse(is);
		return doc;
	}

	//This method is for creating new Domdocument for my filelist XMLDocument
	Document getNewFileListDocument(){
		DocumentBuilderFactory Fact = DocumentBuilderFactory.newInstance();
		DocumentBuilder Bd = null;
		try {
			Bd = Fact.newDocumentBuilder();
		} catch (Exception e) {
			new Logger().makeLog("Can't make new documentbuilder");
			//System.out.println("Can't make new documenbuilder");
			e.printStackTrace();
		}
		Document doc = Bd.newDocument();
		Element rt = doc.createElement("FileListing");
		rt.setAttribute("Version","1");
		rt.setAttribute("CID","E3AWTIQV5FAJ2");
		rt.setAttribute("Base","/");
		rt.setAttribute("Generator","awdc");
		doc.appendChild(rt);
		return doc;
	}

	//This method recursively create Element for Directory in the filelist
	//FileName is name of 
	Element getFileElement(String FileName, String Path, Document Doc){
		new Logger().makeLog("Started big constructor with params" + FileName+","+Path);
		//System.out.println("Started big constructor with params: "+FileName+", "+Path);
		/*
		   * Во первых, смотрим, директория это, или файл
		   * Если директория, то:
		   		1) создаем элемент <Directory Name="Имя директории">
				2) получаем список файлов директории
				3) к созданному элементу добавляем новые элементы из этого списка (через рекурсию, ну ты понел)
				4) возвращаем элемент из пункта 1
		   * Если файл, то:
		   		1) считаем его размер
				2) считаем его hash
				3) создаем элемент с его именем и аттрибутами
				4) возвращаем элемент
		*/
		Element El = null;
		File tmp = new File (Path+"/"+FileName);
		//Всякие проверки
		if (tmp.canRead()){
			if (tmp.isDirectory()){
				System.out.println("Directory: "+FileName);
				El = Doc.createElement("Directory");
				El.setAttribute("Name",FileName);
				Element InEl = null;
				String[] contents = tmp.list();
				for (int i = 0; i < contents.length; i++){
					InEl = new Shara().getFileElement(contents[i],Path+"/"+FileName,Doc);
					El.appendChild(InEl);
				}
			} else if (tmp.isFile()){
				AbstractChecksum checksum = null;
				try {
					checksum = JacksumAPI.getChecksumInstance("tree:tiger");
				} catch (Exception e) {
					e.printStackTrace();
				}
				try {
					checksum.readFile(tmp.getAbsolutePath());
				} catch (Exception e) {
					e.printStackTrace();
				}

				System.out.println("File: "+FileName);
				El = Doc.createElement("File");
				El.setAttribute("Name",FileName);
				El.setAttribute("Size",new String().valueOf(tmp.length()));
				String checksumAsStr = checksum.toString();
				//Надо подправить библиотеку и убрать оттуда адрес к файлу в чексумме
				//временный ворк-эраунд - режем регекспом
//				System.out.println(checksumAsStr);
				Pattern P = Pattern.compile("([\\S]*)\\s.*");
				Matcher m = P.matcher(checksumAsStr);
				m.matches();
				checksumAsStr = m.group(1);
				El.setAttribute("TTH",checksumAsStr);
				//System.out.println(checksum);
			}
		} else
			System.out.println("Can't read");
		return El;
	}
	//Analog of upper method
	Element getFileElement(String FileName, Document Doc){
		Element El = null;
		File tmp = new File(FileName);
		String NewFileName = tmp.getName();
		String Path = tmp.getParent();
		El = new Shara().getFileElement(NewFileName, Path, Doc);
		return El;
	}

	//Method - save XML document from memory to file on hard drive
	public void saveXMLFile(Document Doc, String FileList, String CharSet){
		try{
			Writer target = new OutputStreamWriter(new FileOutputStream(FileList), CharSet);
			Source source = new DOMSource(Doc);
			StreamResult dest = new StreamResult(target);
			Transformer t = TransformerFactory.newInstance().newTransformer();
			t.setOutputProperty(OutputKeys.ENCODING, CharSet);
			t.setOutputProperty(OutputKeys.INDENT, "yes");
			t.transform(source, dest);  
			target.flush();
			target.close();
		} catch (Exception e) {
		e.printStackTrace();
		}
	}

	//TODO at theese 2 methods below we need to do some checks, i think
	//Method - compress xml-file to bz2 by bzip2 algo
	public void xmlToBzip (String FileListXML,String FileListBzip){
		FileInputStream fis = null;
		FileOutputStream fos = null;
		InputStreamReader isr = null;
		CBZip2OutputStream  cbzipos = null;
		byte [] DataOfFileList = null;
		int count = 0;
		boolean append = false;
		try {
			fis = new FileInputStream(FileListXML);
			isr = new InputStreamReader(fis);
			count = fis.available();
			DataOfFileList = new byte[count];
			fis.read(DataOfFileList);
			fos = new FileOutputStream(FileListBzip, append);
			byte [] bz = new byte[2];
			bz = new String("BZ").getBytes();
			fos.write(bz);
			cbzipos = new CBZip2OutputStream(fos);
			cbzipos.write(DataOfFileList);
			cbzipos.close();
		} catch (Exception e){
			e.printStackTrace();
		}
	}

	//TODO see above
	//TODO we need to think some fucking algorythm to
	//make automatic renaming files..
	//btw do we really need to create xml files? We don't!
	//Method - uncompress bz2 to xml file
	String bzipToXml (String FileListBzip, String FileListXml){
		FileInputStream fis = null;
		FileOutputStream fos = null;
		CBZip2InputStream cbzipis = null;
		byte [] Data = null;
		int count = 0;
		boolean append = false;
		try {
			fis = new FileInputStream(FileListBzip);
			fos = new FileOutputStream(FileListXml, append);
			//we need to read 2 bytes (BZ)
			Data = new byte[2];
			fis.read(Data);
			//and now we need to read other bytes by cbzipper stream.. you know;)
			cbzipis = new CBZip2InputStream(fis);
			count = cbzipis.available();
			Data = new byte[count];
			cbzipis.read(Data);
			fos.write(Data);
			fos.close();
		} catch (Exception e){
			e.printStackTrace();
		}
		return "Test.xml";

	}

	//Метод, которым осуществлется поиск. Возвращать он должен filenamewithpath и sizeinbytes. Назовем этот класс AppropiateFileList
	AppropiateFileList doSearch(SearchParams SParams){
		String NameThread = Thread.currentThread().getName();
		new Logger().makeLog("Started do search method of shara!!!!");
		/* суть метода
		 * идем по шаре
		 * перебираем все файлы, для каждого файла создаем объект АФайл
		 * его методом проверяем, подходит ли он нашим параметрам поиска
		 * Если подходит, то кидаем его в AppropiateFileList AFL
		*/
		AppropiateFileList AFL=new AppropiateFileList();
		//Получаем все ноды с именем файл
		NodeList nl = FileList.getElementsByTagName("File");
		for (int i=0; i<nl.getLength(); i++ ){//Для каждой из них смотрим путь к файлу
			new Logger().makeLog("looking in files ("+i+")");
			Element el = (Element) nl.item(i);
			String path = el.getAttribute("Name");
			String TTH = el.getAttribute("TTH");
			int size = Integer.parseInt(el.getAttribute("Size"));
			while (!el.getParentNode().getNodeName().equals("FileListing")){
				el = (Element) el.getParentNode();
				path = el.getAttribute("Name")+"/"+path;
			}
			AppropiateFile AF = new AppropiateFile(path,size, TTH);
			if (AF.doCompare(SParams))
				AFL.addAFile(AF);
			//System.out.println("Найдены такие то: "+path + ";"+size);
		}
		new Logger().makeLog("Совпадений: " + AFL.getCountOfFiles());
		return AFL;
	}
}

//Этот класс должнен представлять собой описание файла, который подходит
//то есть строку с путем к файлу и его размером
class  AppropiateFile{
	String Path;
	int Size;
	String TTH;
	AppropiateFile Next;

	//Конструктор
	AppropiateFile(String Path, int Size, String TTH){
		this.Path = Path;
		this.Size = Size;
		this.TTH = TTH;
		this.Next = null;
	}

	boolean doCompare(SearchParams SParams){
		SearchPattern SP = SParams.getSPatterns();
		String NameThread = Thread.currentThread().getName();
		new Logger().makeLog( "starting doCompare..");
		boolean result = false;
		//Сначала проверяем по размеру и прочему, ибо это быстрее чем по регэкспам
		if (SParams.getSizeRestricted() && SParams.getIsMinimumSize() && (this.Size < SParams.getSize())) //Если указан  минимум сайз и файл меньше
			result = false;
		else if (SParams.getSizeRestricted() && (!SParams.getIsMinimumSize()) && (this.Size > SParams.getSize())) //Если указан максимум и файл больше
			result = false;
		else {//И теперь уже проверяем случаи, когда размер подходид
			//TODO надо вставить проверку типа файла, но потом
			while (SP!=null){

				new Logger().makeLog("one step of while in doCompare..");
				String SPattern = SP.getPattern();
				Pattern p = Pattern.compile(".*"+SPattern+".*|"+SPattern+".*|.*"+SPattern);
				Matcher m = p.matcher("SPattern");
				//Пока сделаем так, что хотя бы одно совпадение с поисковым паттерном будет присутствовать
				if (m.matches())//Пока так покатит, потом можно будет улучшить TODO
					result = true;				
				SP = SP.getNext();	
			}
			
		}

		return result;
	}

	String getPath(){
		return Path;
	}
	int getSize(){
		return Size;
	}

	AppropiateFile getNext(){
		return Next;
	}

	void setNext(AppropiateFile AFile){
		this.Next = AFile;
	}
}

//Этот класс - список подходящих файлов. Метод шары doSearch должен возвращать именно список таких файлов 
class AppropiateFileList{
	AppropiateFile First;//указатель на первый файл
	AppropiateFile Last;
	int CountOfFiles;//счетчик подходящих файлов

	//Конструктор
	AppropiateFileList(){
		First = null;
		Last = null;
		CountOfFiles = 0;
	}
	//TODO надо придумать, как красиво организовать тут список файлов подходящих.. Динамическим списком
	void addAFile(AppropiateFile AFile){
		if (CountOfFiles==0){
			First = AFile;
			Last = First;
		} else {
			Last.setNext(AFile);
			Last = Last.getNext();
		}
		CountOfFiles++;
	}

	int getCountOfFiles(){
		return CountOfFiles;
	}
}

/*
 * ДА и вообще. Как осуществлять этот долбаный поиск?
 * Наверно так. 
 * 1) Просто идем по шаре(хмльной)
 * 2) Для каждого файла создаем аппроприэтфайл
 * 2) Каждый файл сравниваем со всеми паттернами (У СерчПаттерна надо сделать метод сравнения с файлом)
 * 3) Если файл подходит, то добавляем его в АппроприатеФайлЛист
*/

